#!/usr/bin/python
#
# Copyright Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations


DOCUMENTATION = r"""
module: ipmi_power
short_description: Power management for machine
description:
  - Use this module for power management.
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  name:
    description:
      - Hostname or IP address of the BMC.
    required: true
    type: str
  port:
    description:
      - Remote RMCP port.
    default: 623
    type: int
  user:
    description:
      - Username to use to connect to the BMC.
    required: true
    type: str
  password:
    description:
      - Password to connect to the BMC.
    required: true
    type: str
  key:
    description:
      - Encryption key to connect to the BMC in hex format.
    type: str
    version_added: 4.1.0
  state:
    description:
      - Whether to ensure that the machine in desired state.
      - 'The choices for state are:'
      - V(on) -- Request system turn on.
      - V(off) -- Request system turn off without waiting for OS to shutdown.
      - V(shutdown) -- Have system request OS proper shutdown.
      - V(reset) -- Request system reset without waiting for OS.
      - V(boot) -- If system is off, then V(on), else V(reset).
      - Either this option or O(machine) is required.
    choices: ['on', 'off', shutdown, reset, boot]
    type: str
  timeout:
    description:
      - Maximum number of seconds before interrupt request.
    default: 300
    type: int
  machine:
    description:
      - Provide a list of the remote target address for the bridge IPMI request, and the power status.
      - Either this option or O(state) is required.
    type: list
    elements: dict
    version_added: 4.3.0
    suboptions:
      targetAddress:
        description:
          - Remote target address for the bridge IPMI request.
        type: int
        required: true
      state:
        description:
          - Whether to ensure that the machine specified by O(machine[].targetAddress) in desired state.
          - If this option is not set, the power state is set by O(state).
          - If both this option and O(state) are set, this option takes precedence over O(state).
        choices: ['on', 'off', shutdown, reset, boot]
        type: str

requirements:
  - pyghmi
author: "Bulat Gaifullin (@bgaifullin) <gaifullinbf@gmail.com>"
"""

RETURN = r"""
powerstate:
  description: The current power state of the machine.
  returned: success and O(machine) is not provided
  type: str
  sample: 'on'
status:
  description: The current power state of the machine when the machine option is set.
  returned: success and O(machine) is provided
  type: list
  elements: dict
  version_added: 4.3.0
  contains:
    powerstate:
      description: The current power state of the machine specified by RV(status[].targetAddress).
      type: str
    targetAddress:
      description: The remote target address.
      type: int
  sample:
    [
      {
        "powerstate": "on",
        "targetAddress": 48
      },
      {
        "powerstate": "on",
        "targetAddress": 50
      }
    ]
"""

EXAMPLES = r"""
- name: Ensure machine is powered on
  community.general.ipmi_power:
    name: test.testdomain.com
    user: admin
    password: password
    state: 'on'

- name: Ensure machines of which remote target address is 48 and 50 are powered off
  community.general.ipmi_power:
    name: test.testdomain.com
    user: admin
    password: password
    state: 'off'
    machine:
      - targetAddress: 48
      - targetAddress: 50

- name: Ensure machine of which remote target address is 48 is powered on, and 50 is powered off
  community.general.ipmi_power:
    name: test.testdomain.com
    user: admin
    password: password
    machine:
      - targetAddress: 48
        state: 'on'
      - targetAddress: 50
        state: 'off'
"""

import traceback
import binascii

PYGHMI_IMP_ERR = None
INVALID_TARGET_ADDRESS = 0x100
try:
    from pyghmi.ipmi import command
except ImportError:
    PYGHMI_IMP_ERR = traceback.format_exc()
    command = None

from ansible.module_utils.basic import AnsibleModule, missing_required_lib


def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(required=True),
            port=dict(default=623, type="int"),
            state=dict(choices=["on", "off", "shutdown", "reset", "boot"]),
            user=dict(required=True, no_log=True),
            password=dict(required=True, no_log=True),
            key=dict(type="str", no_log=True),
            timeout=dict(default=300, type="int"),
            machine=dict(
                type="list",
                elements="dict",
                options=dict(
                    targetAddress=dict(required=True, type="int"),
                    state=dict(type="str", choices=["on", "off", "shutdown", "reset", "boot"]),
                ),
            ),
        ),
        supports_check_mode=True,
        required_one_of=(["state", "machine"],),
    )

    if command is None:
        module.fail_json(msg=missing_required_lib("pyghmi"), exception=PYGHMI_IMP_ERR)

    name = module.params["name"]
    port = module.params["port"]
    user = module.params["user"]
    password = module.params["password"]
    state = module.params["state"]
    timeout = module.params["timeout"]
    machine = module.params["machine"]

    try:
        if module.params["key"]:
            key = binascii.unhexlify(module.params["key"])
        else:
            key = None
    except Exception:
        module.fail_json(msg="Unable to convert 'key' from hex string.")

    # --- run command ---
    try:
        ipmi_cmd = command.Command(bmc=name, userid=user, password=password, port=port, kg=key)
        module.debug(f'ipmi instantiated - name: "{name}"')

        changed = False
        if machine is None:
            current = ipmi_cmd.get_power()
            if current["powerstate"] != state:
                response = {"powerstate": state} if module.check_mode else ipmi_cmd.set_power(state, wait=timeout)
                changed = True
            else:
                response = current

            if "error" in response:
                module.fail_json(msg=response["error"])

            module.exit_json(changed=changed, **response)
        else:
            response = []
            for entry in machine:
                taddr = entry["targetAddress"]
                if taddr >= INVALID_TARGET_ADDRESS:
                    module.fail_json(msg="targetAddress should be set between 0 to 255.")

                try:
                    # bridge_request is supported on pyghmi 1.5.30 and later
                    current = ipmi_cmd.get_power(bridge_request={"addr": taddr})
                except TypeError:
                    module.fail_json(msg="targetAddress isn't supported on the installed pyghmi.")

                if entry["state"]:
                    tstate = entry["state"]
                elif state:
                    tstate = state
                else:
                    module.fail_json(msg="Either state or suboption of machine state should be set.")

                if current["powerstate"] != tstate:
                    changed = True
                    if not module.check_mode:
                        new = ipmi_cmd.set_power(tstate, wait=timeout, bridge_request={"addr": taddr})
                        if "error" in new:
                            module.fail_json(msg=new["error"])

                        response.append({"targetAddress:": taddr, "powerstate": new["powerstate"]})

                if current["powerstate"] == tstate or module.check_mode:
                    response.append({"targetAddress:": taddr, "powerstate": tstate})

            module.exit_json(changed=changed, status=response)
    except Exception as e:
        module.fail_json(msg=str(e))


if __name__ == "__main__":
    main()
